# # 文件名: camera_controller.py
# # 简化版 - 摄像头控制器（用于视觉问答功能）
# # 【更新】使用 RealSense 相机替换原有相机，增加了实时预览窗口和更健壮的初始化。
# # 【新增】增加测距功能并在预览窗口显示距离

# import pyrealsense2 as rs
# import numpy as np
# import cv2
# import base64
# import sys
# import threading
# import time

# class CameraController:
#     """
#     摄像头控制器，使用 RealSense 相机，增加了实时预览和测距功能。
#     """
#     _instance = None
    
#     def __new__(cls, *args, **kwargs):
#         if cls._instance is None:
#             cls._instance = super(CameraController, cls).__new__(cls)
#         return cls._instance
#     def __init__(self, camera_index=0):
#         if hasattr(self, 'initialized'):
#             return  # 防止重复初始化
#         self.initialized = True

#         print("📷 初始化 RealSense 摄像头...")
#         self.pipeline = None  # 初始化时设置为 None
#         try:
#             # 配置 RealSense 管道和配置，同时启用深度流和彩色流
#             self.pipeline = rs.pipeline()
#             config = rs.config()
#             config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 15)
#             config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 15)  # 启用深度流

#             # 启动管道
#             self.profile = self.pipeline.start(config)

#             # 获取深度传感器的深度比例
#             depth_sensor = self.profile.get_device().first_depth_sensor()
#             self.depth_scale = depth_sensor.get_depth_scale()

#             # 创建对齐对象，将深度帧对齐到彩色帧
#             self.align = rs.align(rs.stream.color)

#         except Exception as e:
#             print(f"❌ 致命错误：无法打开 RealSense 摄像头。错误信息: {e}")
#             self.pipeline = None  # 确保失败时为 None
#             sys.exit(1)

#         self.is_previewing = False
#         self.preview_thread = None
#         self.last_frame = None
#         self.last_depth_frame = None  # 新增：存储最后一帧深度数据
#         self.frame_lock = threading.Lock()

#         # 启动一个后台线程来持续读取帧数据
#         self.read_thread = threading.Thread(target=self._read_worker, daemon=True)
#         self.read_thread.start()

#         time.sleep(1) # 等待摄像头稳定
#         print("✅ RealSense 摄像头初始化成功。")

#     def _read_worker(self):
#         while self.pipeline:
#             try:
#                 frames = self.pipeline.wait_for_frames(timeout_ms=5000)
#                 aligned_frames = self.align.process(frames)

#                 color_frame = aligned_frames.get_color_frame()
#                 depth_frame = aligned_frames.get_depth_frame()

#                 if color_frame and depth_frame:
#                     color_image = np.asanyarray(color_frame.get_data())
#                     depth_image = np.asanyarray(depth_frame.get_data())
#                     with self.frame_lock:
#                         self.last_frame = color_image.copy()
#                         self.last_depth_frame = depth_image.copy()
#                 else:
#                     print("⚠️ 等待帧超时或获取空帧")
#             except rs.error as e:
#                 print(f"❗ RealSense 错误: {e}")
#                 time.sleep(1)
#             except Exception as e:
#                 print(f"❌ 其他错误: {e}")
#                 time.sleep(1)

#     def _restart_pipeline(self):
#         """尝试重启相机管道"""
#         if self.pipeline:
#             try:
#                 self.pipeline.stop()
#             finally:
#                 self.pipeline = None
#         time.sleep(1)
#         # 重新创建一个新实例
#         new_camera = CameraController(camera_index=0)
#         self.pipeline = new_camera.pipeline
#         self.profile = new_camera.profile
#         self.depth_scale = new_camera.depth_scale
#         self.align = new_camera.align
#         self.read_thread = new_camera.read_thread

#     def _preview_worker(self):
#         """在独立的窗口中显示实时预览，并显示测距信息"""
#         window_name = "RealSense Camera Preview"
#         cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)
#         while self.is_previewing and self.pipeline:
#             with self.frame_lock:
#                 if self.last_frame is not None and self.last_depth_frame is not None:
#                     display_frame = self.last_frame.copy()
#                     # 获取图像中心位置
#                     h, w = display_frame.shape[:2]
#                     center_x, center_y = w // 2, h // 2

#                     # 获取中心位置的深度值
#                     depth = self.last_depth_frame[center_y, center_x] * self.depth_scale
#                     distance_text = f"Distance: {depth:.2f} m"

#                     # 在显示的帧上添加提示信息和距离信息
#                     cv2.putText(display_frame, "Live Preview", (10, 30), 
#                                 cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
#                     cv2.putText(display_frame, distance_text, (10, 60), 
#                                 cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
#                     cv2.circle(display_frame, (center_x, center_y), 5, (0, 255, 0), -1)  # 标记中心位置

#                     cv2.imshow(window_name, display_frame)
            
#             # 按 'q' 键可以手动关闭预览窗口
#             if cv2.waitKey(1) & 0xFF == ord('q'):
#                 break
        
#         cv2.destroyAllWindows()
#         self.is_previewing = False # 确保状态同步

#     def start_preview(self):
#         """启动实时预览窗口"""
#         if not self.is_previewing:
#             self.is_previewing = True
#             self.preview_thread = threading.Thread(target=self._preview_worker, daemon=True)
#             self.preview_thread.start()
#             print("👁️ RealSense 摄像头预览窗口已启动。")

#     def stop_preview(self):
#         """停止实时预览"""
#         self.is_previewing = False
#         if self.preview_thread and self.preview_thread.is_alive():
#             self.preview_thread.join(timeout=1)

#     def capture_image_base64(self):
#         """
#         从 RealSense 摄像头捕获一帧图像，并将其编码为JPEG格式的Base64字符串。
#         """
#         with self.frame_lock:
#             if self.last_frame is None:
#                 print("❌ 拍照失败：无法从 RealSense 摄像头读取帧。")
#                 return None
#             frame_to_encode = self.last_frame.copy()

#         success, buffer = cv2.imencode('.jpg', frame_to_encode)
#         if not success:
#             print("❌ 拍照失败：无法将图像编码为JPEG。")
#             return None

#         image_base64 = base64.b64encode(buffer).decode('utf-8')
#         print("📸 成功捕获并编码图像。")
#         return image_base64
    
#     def get_current_frame(self, with_depth=True):
#         """获取当前最新的一帧图像及深度图"""
#         with self.frame_lock:
#             if self.last_frame is not None:
#                 color_image = self.last_frame.copy()
#                 if with_depth and self.last_depth_frame is not None:
#                     depth_image = self.last_depth_frame.copy()
#                     return color_image, depth_image
#                 else:
#                     return color_image, None
#             else:
#                 print("❌ 无法获取图像帧")
#                 return None, None

#     def get_distance(self):
#         """获取相机中心位置的距离"""
#         with self.frame_lock:
#             if self.last_depth_frame is None or self.depth_scale is None:
#                 return None
#             h, w = self.last_depth_frame.shape
#             center_x, center_y = w // 2, h // 2
#             distance = self.last_depth_frame[center_y, center_x] * self.depth_scale
#             return distance

#     def capture_and_save(self, save_path='temp/vl_now.jpg', check=False):
#         """
#         拍摄一张图片并保存，可选人工确认。

#         Args:
#             save_path (str): 图片保存路径。
#             check (bool): 是否需要人工确认（按 c 继续 / q 退出）。
#         """
#         with self.frame_lock:
#             if self.last_frame is None:
#                 print("❌ 拍照失败：无法从 RealSense 摄像头读取帧。")
#                 return

#             img_bgr = self.last_frame.copy()

#             # 保存图片
#             print(f'    保存至 {save_path}')
#             cv2.imwrite(save_path, img_bgr)

#             # 显示图片
#             cv2.destroyAllWindows()
#             cv2.imshow('zihao_vlm', img_bgr)

#             # 人工确认
#             if check:
#                 print('请确认拍照成功，按 c 键继续，按 q 键退出')
#                 while True:
#                     key = cv2.waitKey(10) & 0xFF
#                     if key == ord('c'):
#                         break
#                     if key == ord('q'):
#                         cv2.destroyAllWindows()
#                         raise NameError('按 q 退出')
#             else:
#                 cv2.waitKey(10)  # 简单让窗口刷新一次

#             cv2.destroyAllWindows()

#     def cleanup(self):
#         print("🧹 正在关闭 RealSense 摄像头...")
#         self.stop_preview()
#         if self.pipeline:
#             try:
#                 self.pipeline.stop()
#             except Exception as e:
#                 print(f"⚠️ 停止管道时发生警告: {e}")
#             finally:
#                 self.pipeline = None
#         print("✅ RealSense 摄像头资源已释放。")


# 文件名: camera_controller.py
# 简化版 - 摄像头控制器（用于视觉问答功能）
# 【更新】替换 RealSense 为 OpenCV + DF500 工业相机
# 【保留】拍照、预览、Base64 编码、人工确认等核心功能

import cv2
import numpy as np
import base64
import sys
import threading
import time
import os

class CameraController:
    """
    摄像头控制器，使用 OpenCV + DF500 工业相机。
    支持实时预览、点击坐标打印、拍照、Base64 编码等功能。
    """

    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(CameraController, cls).__new__(cls)
        return cls._instance

    def __init__(self, camera_index=1):
        if hasattr(self, 'initialized'):
            return  # 防止重复初始化
        self.initialized = True

        print("📷 初始化 DF500 工业摄像头...")

        # 打开 DF500 相机
        # Windows 下使用 CAP_DSHOW，Ubuntu 下直接使用默认参数
        if sys.platform == "win32":
            self.cap = cv2.VideoCapture(camera_index, cv2.CAP_DSHOW)
        else:  # Linux/Ubuntu
            self.cap = cv2.VideoCapture(camera_index)

        # 设置分辨率和帧率
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        self.cap.set(cv2.CAP_PROP_FPS, 30)

        if not self.cap.isOpened():
            print("❌ 无法打开 DF500 工业摄像头，请检查：")
            print("   1. 相机是否已连接；")
            print("   2. 尝试更换设备索引号（如 0/1/2）；")
            if sys.platform != "win32":  # Linux/Ubuntu系统额外提示
                print("   3. Ubuntu系统下可能需检查设备权限：")
                print("      - 运行 'ls -l /dev/video*' 查看摄像头设备")
                print("      - 如需权限，运行 'sudo chmod 666 /dev/video*'")
            sys.exit(1)

        self.is_previewing = False
        self.preview_thread = None
        self.last_frame = None
        self.frame_lock = threading.Lock()

        # 启动后台线程持续读取帧
        self.read_thread = threading.Thread(target=self._read_worker, daemon=True)
        self.read_thread.start()

        time.sleep(1)  # 等待相机稳定
        print("✅ DF500 工业摄像头初始化成功。")

    def _read_worker(self):
        while True:
            ret, frame = self.cap.read()
            if ret and frame is not None:
                with self.frame_lock:
                    self.last_frame = frame.copy()
            else:
                print("⚠️ 读取帧失败，可能相机断开或驱动异常")
                time.sleep(1)

    def start_preview(self):
        """启动实时预览窗口，并支持鼠标点击获取坐标"""
        if not self.is_previewing:
            self.is_previewing = True
            self.preview_thread = threading.Thread(target=self._preview_worker, daemon=True)
            self.preview_thread.start()
            print("👁️ DF500 摄像头预览窗口已启动。")

    def stop_preview(self):
        """停止实时预览"""
        self.is_previewing = False
        if self.preview_thread and self.preview_thread.is_alive():
            self.preview_thread.join(timeout=1)

    def _preview_worker(self):
        window_name = "DF500 Industrial Camera Preview"
        cv2.namedWindow(window_name)

        def mouse_callback(event, x, y, flags, param):
            if event == cv2.EVENT_LBUTTONDOWN:
                print(f"📸 点击像素坐标：x={x}, y={y}")

        cv2.setMouseCallback(window_name, mouse_callback)

        while self.is_previewing:
            with self.frame_lock:
                if self.last_frame is not None:
                    display_frame = self.last_frame.copy()

                    # 绘制中心十字线
                    h, w = display_frame.shape[:2]
                    cx, cy = w // 2, h // 2
                    cross_len = 20
                    cv2.line(display_frame, (cx - cross_len, cy), (cx + cross_len, cy), (0, 0, 255), 2)
                    cv2.line(display_frame, (cx, cy - cross_len), (cx, cy + cross_len), (0, 0, 255), 2)

                    # 显示提示文字
                    cv2.putText(display_frame, "Live Preview", (10, 30),
                                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

                    cv2.imshow(window_name, display_frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        cv2.destroyAllWindows()
        self.is_previewing = False

    def capture_image_base64(self):
        """
        捕获一帧图像并编码为 Base64 字符串。
        """
        with self.frame_lock:
            if self.last_frame is None:
                print("❌ 拍照失败：无法从 DF500 读取帧。")
                return None
            frame_to_encode = self.last_frame.copy()

        success, buffer = cv2.imencode('.jpg', frame_to_encode)
        if not success:
            print("❌ 拍照失败：无法将图像编码为JPEG。")
            return None

        image_base64 = base64.b64encode(buffer).decode('utf-8')
        print("📸 成功捕获并编码图像。")
        return image_base64

    def get_current_frame(self):
        """获取当前最新的一帧图像"""
        with self.frame_lock:
            if self.last_frame is not None:
                return self.last_frame.copy()
            else:
                print("❌ 无法获取图像帧")
                return None

    def capture_and_save(self, save_path='temp/vl_now.jpg', check=False):
        """
        拍摄一张图片并保存，可选人工确认。

        Args:
            save_path (str): 图片保存路径。
            check (bool): 是否需要人工确认（按 c 继续 / q 退出）。
        """
        with self.frame_lock:
            if self.last_frame is None:
                print("❌ 拍照失败：无法从 DF500 读取帧。")
                return

            img_bgr = self.last_frame.copy()

            # 创建目录（如果不存在）
            os.makedirs(os.path.dirname(save_path), exist_ok=True)

            # 保存图片
            print(f'    保存至 {save_path}')
            cv2.imwrite(save_path, img_bgr)

            # 显示图片
            cv2.destroyAllWindows()
            cv2.imshow('zihao_vlm', img_bgr)

            # 人工确认
            if check:
                print('请确认拍照成功，按 c 键继续，按 q 键退出')
                while True:
                    key = cv2.waitKey(10) & 0xFF
                    if key == ord('c'):
                        break
                    if key == ord('q'):
                        cv2.destroyAllWindows()
                        raise NameError('按 q 退出')
            else:
                cv2.waitKey(10)  # 简单刷新窗口

            cv2.destroyAllWindows()

    def cleanup(self):
        print("🧹 正在关闭 DF500 工业摄像头...")
        self.stop_preview()
        if self.cap.isOpened():
            self.cap.release()
        cv2.destroyAllWindows()
        print("✅ DF500 工业摄像头资源已释放。")

